// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
 *    This file implements FunctionEntry class
 */

#include <string>
#include "Gen.hpp"
#include "Exception.hpp"
#include "FpOutputStream.hpp"
#include "DataOutputStream.hpp"

using namespace ninf::gen;
using namespace std;

void 
FunctionEntry :: generateInfoFile(){
  string filename = string("_stub_") + 
    interface.moduleName + "_" +
    interface.entryName + ".info";
  FILE * fp = fopen(filename.c_str(), "w");

  if (fp == NULL)
    EXCEPTION(string("failed to open ") + filename);
  writeOutInfoFile(fp);
  fclose(fp);
}

void 
FunctionEntry :: generateStubProgram(){
  string filename = string("_stub_") + 
    interface.moduleName + "_" +
    interface.entryName + 
    ".cpp";
  FILE * fp = fopen(filename.c_str(), "w");
  if (fp == NULL)
    EXCEPTION(string("failed to open ") + filename);
  writeOutStubProgram(fp);
  fclose(fp);
}

void 
FunctionEntry :: writeOutInfoFile(FILE * fp){
  FpOutputStream tmp(fp);
  DataOutputStream dos(&tmp);
  interface.writeTo(dos);
}

void 
FunctionEntry :: writeOutStubProgram(FILE * fp){
  // header
  writeOutHeader(fp);

  // global
  writeOutGlobals(fp);


  // the main
  writeOutMain(fp);
}

void 
FunctionEntry :: writeOutHeader(FILE * fp){
    fprintf(fp,"/* This file is generated by Gen. don't edit. */\n");
    fprintf(fp,"#include \"StubObj.hpp\"\n\n");
    fprintf(fp,"using namespace ninf::stub;\n\n");
    fprintf(fp,"using namespace ninf::lib;\n\n");
    fprintf(fp,"using namespace ninf::gen;\n\n");

}  

void 
FunctionEntry :: writeOutGlobals(FILE * fp){
  /* generate globals */
  fprintf(fp,"/* Globals */\n");
  for (unsigned i = 0; i < ncGenGlobals.globals.size(); i++) 
    fprintf(fp,"%s\n",ncGenGlobals.globals[i]);
}

void 
FunctionEntry :: writeOutVarDecl(FILE * fp){
  for (int i = 0; i < interface.nParams; i++){
    ParamDesc * desc = interface.params[i];
    fprintf(fp, "\t%s %s%s;\n", 
	    DataCNames[desc->dataType],
	    (desc->nDim > 0) ? "*":"",
	    paramNames[i]);
  }
}

int 
FunctionEntry :: findParam(const char * paramName){
  for (unsigned int i = 0; i < paramNames.size(); i++){
    if (strcmp(paramNames[i], paramName) == 0)
      return i;
  }
  return -1;
}


void 
FunctionEntry :: writeOutParam(const char * paramName, 
				 FILE * fp
){
  int index = findParam(paramName);
  if (index < 0)
    EXCEPTION(string("cannot find param") + paramName);
  ParamDesc * desc = interface.params[index];

  bool withAmp = false;
  if (strcasecmp(language, FORTRAN) == 0 &&
      desc->nDim == 0 &&
      desc->modeType == MODE_IN &&
      desc->dataType != DT_STRING_TYPE &&
      desc->dataType != DT_FILENAME){
    withAmp = true;
  }
  fprintf(fp, "%s%s", 
	  withAmp ? "&":"",
	  paramName);
}

void 
FunctionEntry :: writeOutLen(const char * paramName, FILE * fp){
  int index = findParam(paramName);
  if (index < 0)
    EXCEPTION(string("cannot find param") + paramName);
  ParamDesc * desc = interface.params[index];

  if ((strcasecmp(language, FORTRAN) == 0) &&
      desc->dataType == DT_STRING_TYPE)
    fprintf(fp, ", strlen(%s)", paramName);
}

const char *
FunctionEntry :: fortranFormat(char * buf, const char * fformat, const char * fname)
{
  char * answer = buf;
  if (fformat == NULL)
    return fname;
  while (*fformat != '\0'){
    if (*fformat != '%'){
      *buf++ = *fformat++;
      continue;
    } else {
      char * tmp;
      switch (*(++fformat)){
      case 's': 
      case 'S': 
		  tmp = (char *)fname;
	while (*tmp != '\0')
	  *buf++ = *tmp++;
	break;
      case 'l': 
      case 'L': 
		  tmp = (char * )fname;
	while (*tmp != '\0'){
	  char tc = *tmp++;
	  *buf++ = toupper(tc);
	}
	break;
      default:
	fprintf(stderr, "unknown fortran format %%%c: ignore\n", *fformat);
      }
      fformat++;
    }   
  }
  *buf = '\0';
  return (const char *) answer;
}

void
FunctionEntry :: writeOutFunctionName(const char * funcname, FILE * fp){
  char buf [1000];
  if (strcasecmp(language, FORTRAN) == 0)
    fprintf(fp, "%s", 
	    fortranFormat(buf, ncGenGlobals.currentFortranFormat, 
			  funcname));
  else 
    fprintf(fp, "%s", funcname);
}

void 
FunctionEntry :: writeOutBody(FILE * fp){
  if (bodyExpr != NULL){
    fprintf(fp, "\t");
    writeOutFunctionName(SYM_NAME(EXPR_SYM(EXPR_ARG2(bodyExpr))), fp);
    fprintf(fp, "(");    

    GenExprList * lp;
    FOR_ITEMS_IN_LIST(lp,EXPR_ARG3(bodyExpr)){
      const char * paramName = SYM_NAME(EXPR_SYM(LIST_ITEM(lp)));
      writeOutParam(paramName, fp);
      
      if (ncGenGlobals.fortranStringConvention == 
	  FORTRAN_STRING_CONVENTION_JUST_AFTER )
	writeOutLen(paramName, fp);
      
      if (LIST_NEXT(lp) != NULL)
	fprintf(fp, ", ");
    }      
    if (ncGenGlobals.fortranStringConvention == 
	 FORTRAN_STRING_CONVENTION_LAST ){
      FOR_ITEMS_IN_LIST(lp,EXPR_ARG3(bodyExpr)){
	const char * paramName = SYM_NAME(EXPR_SYM(LIST_ITEM(lp)));
	writeOutLen(paramName, fp);
      }
    }
    fprintf(fp, ");\n");
  } else {
    fprintf(fp, "%s\n", body);
  }
}

void 
FunctionEntry :: writeOutSetArgs(FILE * fp){
  for (int i = 0; i < interface.nParams; i++){
    fprintf(fp,"\t\tstubObj.setArg(&%s, %d);\n",
	    paramNames[i], i);
  }
}

void 
FunctionEntry :: writeOutMain(FILE * fp){
  /* generate main program */
  fprintf(fp,"\n/* Stub Main program */\nint main(int argc, char ** argv){\n");

  fprintf(fp,"\n/*************************************************/\n");
  // function info declaration
  interface.writeDeclaration(1, 1, fp);
  fprintf(fp,"\n/*************************************************/\n");

  writeOutVarDecl(fp);

  fprintf(fp, "\tStubObj stubObj(argc, argv, & func_1);\n");

  /**  Backend-dependent initialization part  **/

  writeOutSetArgs(fp);
  
  fprintf(fp,"\tstubObj.begin();\n");

  fprintf(fp,"\n");
  writeOutBody(fp);
  fprintf(fp,"\n");
    
  fprintf(fp,"\tstubObj.end();\n");

  fprintf(fp,"\treturn 0;\n");
  fprintf(fp,"}\n/* END OF Stub Main */\n");
}



